/**
 * Copyright 2012 Roger Cockshutt
 * krakenbrau@gmail.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.googlecode.parallelrl.data;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.HashMap;
import java.util.List;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.googlecode.parallelrl.data.ecs.ECSManager;
import com.googlecode.parallelrl.process.EventSystem;
import com.googlecode.parallelrl.process.Game;
import com.googlecode.parallelrl.util.ComponentPair;

/**
 * An instance of the DataFile class represents some file from which we need to read some data, or 
 * a file to which we need to write (ie. a save game).
 * 
 * @author Roger Cockshutt
 *
 */
public class DataFile {
	
	private List<String> lines = new Vector<>();
	private String name;
	
	/**
	 * @param name Path of the file.  Will be created if non-existant.
	 */
	public DataFile(String path) {
		name = path;
	}
	
	public List<String> getLines() {
		return lines;
	}
	
	/**
	 * Saves the given level, and all associated entities.
	 * 
	 * @param level Level to be saved.
	 */
	public void saveLevel(ILevel level) {
		lines.add("STEP: '" + Game.currentStep + "'");
		lines.add(level.toString());
		for (Long entity : ECSManager.getAllEntities()) {
			if (EventSystem.isEvent(entity)) {
				lines.add("EVENT <" + entity + "> {");
			} else {
				lines.add("ENTITY <" + entity + "> {");
			}
			for (ComponentPair cmp : ECSManager.getAllComponents(entity)) {
				lines.add("  " + cmp.toString());
			}
			lines.add("}");
		}
	}
	
	/**
	 * Loads a level from this file.
	 * 
	 * @return The loaded level.
	 */
	public ILevel loadLevel() {
		Pattern step = Pattern.compile("STEP:\\s*'([0-9]+)'");
		Matcher match;
		ECSManager.killAll();
		EventSystem.clear();
		Integer fromInd = null;
		Integer toInd = null;
		for (int i = 0; i < lines.size(); i++) {
			String line = lines.get(i);
			match = step.matcher(line);
			if (match.find()) {
				Game.currentStep = Long.valueOf(match.group(1));
				continue;
			}
			if (line.equals("LEVEL{") && fromInd == null) {
				fromInd = i + 1;
				continue;
			}
			if (line.equals("}") && fromInd != null && toInd == null) {
				toInd = i;
				continue;
			}
		}
		ILevel level = null;
		if (fromInd != null && toInd != null) {
			level = ECSLevel.valueOf(lines.subList(fromInd, toInd).toArray(new String[1]));
		}
		HashMap<String, Vector<ComponentPair>> entries = ECSManager.parseData(lines);
		for (String id : entries.keySet()) {
			Long entity = Long.valueOf(id);
			ECSManager.createEntity(entity);
			for (ComponentPair cmp : entries.get(id)) {
				ECSManager.setComponent(entity, cmp);
			}
			if (ECSManager.hasComponents(entity, "Time", "Action", "Entity")) {
				EventSystem.newEvent(entity, ECSManager.getComponent(entity, "Time").longValue(),
						ECSManager.getComponent(entity, "Entity").longValue(), 
						ECSManager.getComponentValue(entity, "Action"), 
						ECSManager.getComponentValue(entity, "Parameters"));
				if (ECSManager.hasComponents(entity, "ConditionObject", "ConditionComparator", "ConditionValue")) {
					EventSystem.setCondition(entity, ECSManager.getComponentValue(entity, "ConditionObject"),
							ECSManager.getComponentValue(entity, "ConditionComparator"),
							ECSManager.getComponentValue(entity, "ConditionValue"));
				}
			}
		}
		return level;
	}
	
	/**
	 * Loads Config data from the file.
	 * 
	 * @return Map of all config fields loaded.
	 */
	public HashMap<String, String> loadConfig() {
		Pattern configLine = Pattern.compile("([^\\s:\\[\\]\\<\\>]+):\\s*([^\\n:\\[\\]\\<\\>]+)");
		HashMap<String, String> configSettings = new HashMap<>();
		Matcher match;
		for (String line : lines) {
			match = configLine.matcher(line);
			if (match.find()) {
				configSettings.put(match.group(1), match.group(2));
			}
		}
		return configSettings;
	}
	
	/**
	 * Loads key bindings from the file.
	 * 
	 * @return Map of all key bindings loaded.
	 */
	public HashMap<Integer, String> loadKeys() {
		Pattern keyLine = Pattern.compile("^([^:]+):([^\\n]*)");
		Pattern specialKey = Pattern.compile("[A-Z][A-Za-z0-9]+");
		Pattern literalKey = Pattern.compile("[^\\s]");
		HashMap<Integer, String> keyMap = new HashMap<>();
		Matcher match;
		String action;
		for (String line : lines) {
			action = null;
			match = keyLine.matcher(line);
			if (match.find()) {
				action = match.group(1);
				if (action != null) {
					String[] keyDefs = match.group(2).trim().split("[- \\t]+");
					KeyCode modifier = null;
					for (String key : keyDefs) {
						match = specialKey.matcher(key);
						if (match.find()) {
							KeyCode code = null;
							try {
								code = KeyCode.valueOf(match.group());
							} catch (IllegalArgumentException|NullPointerException exception) {
								code = null;
							}
							if (code != null) {
								switch (code) {
								case Alt:
								case Ctrl:
								case Shift:
									if (modifier == null) {
										modifier = code;
									} else {
										modifier = null;
									}
									continue;
								default:
									if (modifier == null) {
										keyMap.put(code.codePoint, action);
									} else {
										keyMap.put(code.codePoint ^ modifier.codePoint, action);
										modifier = null;
									}
									continue;
								}
							}
						}
						match = literalKey.matcher(key);
						if (match.find()) {
							int code = match.group().codePointAt(0);
							if (modifier == null) {
								keyMap.put(code, action);
							} else {
								keyMap.put(code ^ modifier.codePoint, action);
							}
							continue;
						}
						System.out.println("Couldn't map key: " + key);
					}
				}
			}
		}
		return keyMap;
	}
	
	/**
	 * Loads Entity data from the file.
	 * 
	 * @return Map of the component constructions of all loaded entities.
	 */
	public HashMap<String, Vector<ComponentPair>> loadEntityData() {
		return ECSManager.parseData(lines);
	}
	
	/**
	 * Write the data to file.  Overwrite existing file.
	 */
	public void write() {
		try {
			Files.write(Paths.get(name, ""), lines, StandardCharsets.UTF_8, 
					StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE);
		} catch (IOException|NullPointerException exception) {
			System.err.println(exception.getMessage());
			exception.printStackTrace();
		}
	}
	
	/**
	 * Read the data from file.  Must be called before any of the 'load' methods.
	 */
	public void read() {
		try {
			lines = new Vector<String>(Files.readAllLines(Paths.get(name, ""), StandardCharsets.UTF_8));
		} catch (IOException|NullPointerException exception) {
			System.err.println(exception.getMessage());
			exception.printStackTrace();
		}
	}

}
